﻿namespace Spectral
{
    using System;

    /// <summary>
    /// Provides creational methods for Boxes.
    /// </summary>
    public static class Box
    {
        /// <summary>
        /// Creates a new box.
        /// </summary>
        /// <typeparam name="T">The type of element used for the box </typeparam>
        /// <param name="x">The x position of the box.</param>
        /// <param name="y">The y position of the box.</param>
        /// <param name="z">The z position of the box.</param>
        /// <param name="width">The width of the box.</param>
        /// <param name="height">The height of the box.</param>
        /// <param name="depth">The depth of the box.</param>
        /// <returns>The constructed box.</returns>
        public static Box<T> Create<T>(T x, T y, T z, T width, T height, T depth)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Box<T>(x, y, z, width, height, depth);
        }

        /// <summary>
        /// Creates a new box.
        /// </summary>
        /// <typeparam name="T">The type of element used for the box </typeparam>
        /// <param name="position">The position of the box.</param>
        /// <param name="size">The size of the box.</param>
        /// <returns>The constructed box.</returns>
        public static Box<T> Create<T>(Vector3<T> position, Vector3<T> size)
            where T : struct, IEquatable<T>, IComparable<T>
        {
            return new Box<T>(position, size);
        }
    }

    /// <summary>
    /// Represents a 3-dimensional axis-aligned box.
    /// </summary>
    /// <typeparam name="T">The type of element used for the box </typeparam>
    public struct Box<T> : IEquatable<Box<T>>
        where T : struct, IEquatable<T>, IComparable<T>
    {
        /// <summary>
        /// The x position of the box.
        /// </summary>
        public readonly T X;

        /// <summary>
        /// The y position of the box.
        /// </summary>
        public readonly T Y;

        /// <summary>
        /// The z position of the box.
        /// </summary>
        public readonly T Z;

        /// <summary>
        /// The width of the box.
        /// </summary>
        public readonly T Width;

        /// <summary>
        /// The height of the box.
        /// </summary>
        public readonly T Height;

        /// <summary>
        /// The depth of the box.
        /// </summary>
        public readonly T Depth;

        /// <summary>
        /// The position of the box.
        /// </summary>
        public Vector3<T> Position { get { return new Vector3<T>(X, Y, Z); } }

        /// <summary>
        /// The size of the box.
        /// </summary>
        public Vector3<T> Size { get { return new Vector3<T>(Width, Height, Depth); } }

        /// <summary>
        /// Constructs a new box.
        /// </summary>
        /// <param name="position">The position of the box.</param>
        /// <param name="size">The size of the box.</param>
        public Box(Vector3<T> position, Vector3<T> size)
            : this(position.X, position.Y, position.Z, size.X, size.Y, size.Z)
        { }
        
        /// <summary>
        /// Constructs a new box.
        /// </summary>
        /// <param name="x">The x position of the box.</param>
        /// <param name="y">The y position of the box.</param>
        /// <param name="z">The z position of the box.</param>
        /// <param name="width">The width of the box.</param>
        /// <param name="height">The height of the box.</param>
        /// <param name="depth">The depth of the box.</param>
        public Box(T x, T y, T z, T width, T height, T depth)
        {
            X = x;
            Y = y;
            Z = z;
            Width = width;
            Height = height;
            Depth = depth;
        }

        /// <summary>
        /// Casts this box to another of a different element type.
        /// </summary>
        /// <typeparam name="U">The type of element to cast to.</typeparam>
        /// <returns>The casted box.</returns>
        public Box<U> Cast<U>()
            where U : struct, IEquatable<U>, IComparable<U>
        {
            return new Box<U>(
                (U)Convert.ChangeType(X, typeof(U)),
                (U)Convert.ChangeType(Y, typeof(U)),
                (U)Convert.ChangeType(Z, typeof(U)),
                (U)Convert.ChangeType(Width, typeof(U)),
                (U)Convert.ChangeType(Height, typeof(U)),
                (U)Convert.ChangeType(Depth, typeof(U)));
        }

        /// <summary>
        /// Determines whether this box and another are equal.
        /// </summary>
        /// <param name="other">The box to compare to.</param>
        /// <returns><c>True</c> if the boxes are equal, <c>False</c> otherwise.</returns>
        public bool Equals(Box<T> other) { return false; }

        /// <summary>
        /// Determines whether this box is equal to another object.
        /// </summary>
        /// <param name="obj">The object to compare to.</param>
        /// <returns><c>True</c> if the objects are equal, <c>False</c> otherwise.</returns>
        public override bool Equals(object obj) { return obj is Box<T> ? Equals((Box<T>)obj) : false; }

        /// <summary>
        /// Calculates the hash code for this instance.
        /// </summary>
        /// <returns></returns>
        public override int GetHashCode() { return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode() ^ Width.GetHashCode() ^ Height.GetHashCode() ^ Depth.GetHashCode(); }

        /// <summary>
        /// Determines whether two boxes are equal.
        /// </summary>
        /// <param name="left">The left side of the operation.</param>
        /// <param name="right">The right side of the operation.</param>
        /// <returns>The result of the comparison.</returns>
        public static bool operator ==(Box<T> left, Box<T> right) { return left.Equals(right); }

        /// <summary>
        /// Determines whether two boxes are not equal.
        /// </summary>
        /// <param name="left">The left side of the operation.</param>
        /// <param name="right">The right side of the operation.</param>
        /// <returns>The result of the comparison.</returns>
        public static bool operator !=(Box<T> left, Box<T> right) { return !left.Equals(right); }
    }
}
